
#ifndef __LIBERTY_ENGINE_VECTOR3_H__
#define __LIBERTY_ENGINE_VECTOR3_H__

#include <Liberty3D/Liberty3D.h>

#include <algorithm>
#include <cmath>

#ifdef min
#undef min
#endif

#ifdef max
#undef max
#endif

namespace Liberty { namespace Engine {
    Liberty::Engine::Vector3 operator * (const Liberty::Engine::Vector3& a, float d);
    Liberty::Engine::Vector3 operator * (float d, const Liberty::Engine::Vector3& a);
    Liberty::Engine::Vector3 operator / (const Liberty::Engine::Vector3& a, float d);
    Liberty::Engine::Vector3 operator / (float d, const Liberty::Engine::Vector3& a);

    template <typename T> T clamp(const T& v, const T& a, const T& b) {
        return std::max(a, std::min(v, b));
    }

    class Vector3 {
    public:
        static const float kEpsilon;

    public:
        static const Vector3 back;
        static const Vector3 forward;

        static const Vector3 up;
        static const Vector3 down;

        static const Vector3 left;
        static const Vector3 right;

        static const Vector3 zero;
        static const Vector3 one;

    public:
        Vector3() : _x(0), _y(0), _z(0) {
        }

        Vector3(float x, float y) : _x(x), _y(y), _z(0) {
        }

        Vector3(float x, float y, float z) : _x(x), _y(y), _z(z) {
        }

    protected:
        float _x;
        float _y;
        float _z;

    public:
        inline float x() const { return _x; }
        inline float y() const { return _y; }
        inline float z() const { return _z; }

        inline void setX(float x) { _x = x; }
        inline void setY(float y) { _y = y; }
        inline void setZ(float z) { _z = z; }

    public:
        inline float  operator[](int index) const { return (&_x)[index]; }
        inline float& operator[](int index)       { return (&_x)[index]; }

    public:
        inline float magnitude()    const { return sqrt(_x * _x + _y * _y + _z * _z); }
        inline float sqrMagnitude() const { return _x * _x + _y * _y + _z * _z; }

        inline Vector3 normalized() const { return normalize(*this); }

    public:
        inline Vector3 operator - () const { return Vector3(-_x, -_y, -_z); }

        inline Vector3 operator + (const Vector3& b) const { return Vector3(_x + b._x, _y + b._y, _z + b._z); }
        inline Vector3 operator - (const Vector3& b) const { return Vector3(_x - b._x, _y - b._y, _z - b._z); }

        inline bool operator == (const Vector3& b) const { return (*this - b).sqrMagnitude() <  9.999999E-11f; }
        inline bool operator != (const Vector3& b) const { return (*this - b).sqrMagnitude() >= 9.999999E-11f; }

    public:
        inline Vector3 clampMagnitude(float maxLength) const {
            if (sqrMagnitude() <= maxLength * maxLength) {
                return *this;
            }
            else {
                return normalized() * maxLength;
            }
        }

        inline Vector3 normalize(const Vector3& value) const {
            float single = value.magnitude();

            return (single <= 1E-05f)? Vector3::zero: value / single;
        }

    public:
        static float angle(const Vector3& from, const Vector3& to) {
            return acos(clamp(Vector3::dot(from.normalized(), to.normalized()), -1.0f, 1.0f)) * 57.29578f;
        }

        static inline float   dot  (const Vector3& lhs, const Vector3& rhs) { return lhs._x * rhs._x + lhs._y * rhs._y + lhs._z * rhs._z; }
        static inline Vector3 cross(const Vector3& lhs, const Vector3& rhs) { return Vector3(lhs._y * rhs._z - lhs._z * rhs._y, lhs._z * rhs._x - lhs._x * rhs._z, lhs._x * rhs._y - lhs._y * rhs._x); }

        static inline float distance(const Vector3& a, const Vector3& b) {
            Vector3 vector3(a._x - b._x, a._y - b._y, a._z - b._z);
            return sqrt(vector3._x * vector3._x + vector3._y * vector3._y + vector3._z * vector3._z);
        }

        static inline Vector3 min(const Vector3& lhs, const Vector3& rhs) {
            return Vector3(
                std::min(lhs._x, rhs._x),
                std::min(lhs._y, rhs._y),
                std::min(lhs._z, rhs._z));
        }

        static inline Vector3 max(const Vector3& lhs, const Vector3& rhs) {
            return Vector3(
                std::max(lhs._x, rhs._x),
                std::max(lhs._y, rhs._y),
                std::max(lhs._z, rhs._z));
        }

        static inline Vector3 lerp(const Vector3& from, const Vector3& to, float t) {
            t = std::max(0.0f, std::min(1.0f, t));

            return Vector3(
                from._x + (to._x - from._x) * t,
                from._y + (to._y - from._y) * t,
                from._z + (to._z - from._z) * t);
        }

        static inline Vector3 scale(const Vector3& a, const Vector3& b) { return Vector3(a._x * b._x, a._y * b._y, a._z * b._z); }
    };
}}

inline Liberty::Engine::Vector3 operator * (const Liberty::Engine::Vector3& a, float d) {
    return Liberty::Engine::Vector3(a.x() * d, a.y() * d, a.z() * d);
}

inline Liberty::Engine::Vector3 operator * (float d, const Liberty::Engine::Vector3& a) {
    return Liberty::Engine::Vector3(a.x() * d, a.y() * d, a.z() * d);
}

inline Liberty::Engine::Vector3 operator / (const Liberty::Engine::Vector3& a, float d) {
    return Liberty::Engine::Vector3(a.x() / d, a.y() / d, a.z() / d);
}

inline Liberty::Engine::Vector3 operator / (float d, const Liberty::Engine::Vector3& a) {
    return Liberty::Engine::Vector3(a.x() / d, a.y() / d, a.z() / d);
}

#endif
